// 
// KFSplitView.m
// KFSplitView v. 1.3, 11/27/2004
// 
// Copyright (c) 2003-2004 Ken Ferry. Some rights reserved.
// http://homepage.mac.com/kenferry/software.html
//
// Other contributors: Kirk Baker, John Pannell
// 
// This work is licensed under a Creative Commons license:
// http://creativecommons.org/licenses/by-nc/1.0/
//
// Send me an email if you have any problems (after you've read what there is to read).
//
// You can reach me at kenferry at the domain mac.com.
// 
// On this whole major axis, minor axis thing:
// 
//     The 'major' axis refers to the direction in which dividers can move.
//     It's the y-axis when [self isVertical] returns NO, and the x-axis otherwise.
//     Pretty much everything that uses coordinates or dimensions in this file works
//     more comfortably in that coordinate system.
// 
// Other
// 
//     This class is a basically a complete reimplementation of NSSplitView.  The
//     underlying NSSplitView is mostly used for drawing dividers.


#import "KFSplitView.h"

#pragma mark File-level and global vars:

NSString *KFSplitViewDidCollapseSubviewNotification = @"KFSplitViewDidCollapseSubviewNotification";
NSString *KFSplitViewDidExpandSubviewNotification = @"KFSplitViewDidExpandSubviewNotification";

const NSPoint KFOffScreenPoint = {1000000.0,1000000.0};

static NSMutableSet *kfInUsePositionNames;

#if !defined(NSINTEGER_DEFINED)
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif

#pragma mark Utility:

// these are macros (instead of inlines) so that we can use the instance variable kfIsVertical
// they're undef'd at the bottom of the file
#define KFMAJORCOORDOFPOINT(point) (kfIsVertical ? (point).x : (point).y)
#define KFMINORCOORDOFPOINT(point) (kfIsVertical ? (point).y : (point).x)
#define KFMAJORDIMOFSIZE(size) (kfIsVertical ? (size).width : (size).height)
#define KFMINORDIMOFSIZE(size) (kfIsVertical ? (size).height : (size).width)
#define KFPOINTWITHMAJMIN(major, minor) (kfIsVertical ? NSMakePoint((major), (minor)) : NSMakePoint((minor), (major)))
#define KFSIZEWITHMAJMIN(major, minor) (kfIsVertical ? NSMakeSize((major), (minor)) : NSMakeSize((minor), (major)))

#define KFMAX(a,b) ((a)>(b)?(a):(b))

// proportionally scale a list of integers so that the sum of the resulting list is targetTotal
// Will fail (return NO) if all integers are zero 
// Favors not completely zeroing out a nonzero int
static BOOL kfScaleUInts(unsigned *integers, int numInts, unsigned targetTotal) 
{
    unsigned total;
    float scalingFactor;
    int i, numNonZeroInts;
    
    // compute total
    total = 0;
    numNonZeroInts = 0;
    for (i = 0; i < numInts; i++)
    {
        if (integers[i] != 0)
        {
            total += integers[i];
            numNonZeroInts++;
        }
    }
    
    if (numNonZeroInts == 0) // fail
    {
        return NO;
    }
    
    // compute scalingFactor
    scalingFactor = (float)targetTotal / total;
    
    // scale all ints and recompute total (which may not equal targetTotal due to roundoff error)
    total = 0;
    for (i = 0; i < numInts; i++)
    {
        if (integers[i] != 0)
        {
            // this is preferable to rounding when used for subviews - helps
            // prevent a subview getting stuck at thickness 1 during a drag resize
            integers[i] = MAX(floor(scalingFactor*integers[i]), 1); 
            total += integers[i];
        }
    }
    
    // Each non-zero integer may be as much as 1 off of its "proper" floating point value due to roundoff,
    // so abs(targetTotal - total) might be as much as numNonZero.  We randomly choose integers to increment (or decrement)
    // to make up the gap, and we choose only from the non-zero values.
    int gap = abs(targetTotal - total);
    int closeGapIncrement =  (targetTotal > total) ? 1 : -1;
    int numRemainingNonZeroInts = numNonZeroInts;
    for (i = 0; i < numInts && gap > 0; i++)
    {
        if (integers[i] > 0)
        {
            BOOL shouldIncrementInt =  (gap == numRemainingNonZeroInts) || (rand() < (float) gap / numRemainingNonZeroInts * RAND_MAX);
            if (shouldIncrementInt)
            {
                integers[i] += closeGapIncrement;
                gap--;
            }
            numRemainingNonZeroInts--;
        }
    }
    
    return YES;
}

@interface KFSplitView (kfPrivate)

+ (NSString *)kfDefaultsKeyForName:(NSString *)name;
- (void)kfSetup;
- (void)kfSetupResizeCursors;
- (int)kfGetDividerAtMajCoord:(float)coord;
- (void)kfPutDivider:(NSUInteger)offset atMajCoord:(float)coord;
- (void)kfRecalculateDividerRects;
- (void)kfMoveCollapsedSubviewsOffScreen;
- (void)kfSavePositionUsingAutosaveName:(id)sender;

- (void)kfLayoutSubviewsUsingThicknesses:(unsigned *)subviewThicknesses;

@end

@implementation KFSplitView

/*****************
 * Initialization
 *****************/
#pragma mark Setup/teardown:

+ (void)initialize
{
    kfInUsePositionNames = [[NSMutableSet alloc] init];
}

- initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]))
    {
        [self kfSetup];
    }

    return self;
}

- initWithCoder:(NSCoder *)coder
{
    if ((self = [super initWithCoder:coder]))
    {
        [self kfSetup];
    }

    return self;
}

- (void)kfSetup
{
    // be sure to setup cursors before calling setVertical:
    [self kfSetupResizeCursors];

    kfCollapsedSubviews = [[NSMutableSet alloc] init];
    kfDividerRects = [[NSMutableArray alloc] init];
    
    kfDefaults = [NSUserDefaults standardUserDefaults];
    kfNotificationCenter = [NSNotificationCenter defaultCenter];

    [self setVertical:[self isVertical]];
}

// Attempts to find cursors to use as kfIsVerticalResizeCursor and kfNotIsVerticalResizeCursor.
// These cursors are eventually released, so make sure each receives a retain message now.
// If no good cursors can be found, an error is printed and the arrow cursor is used.
- (void)kfSetupResizeCursors
{
    NSImage *isVerticalImage = [NSImage imageNamed:@"NSTruthHorizontalResizeCursor"];
         // standard Jaguar NSSplitView resize cursor
    NSImage *isNotVerticalImage = [NSImage imageNamed:@"NSTruthVerticalResizeCursor"];
         // standard Jaguar NSSplitView resize cursor

    if (!isVerticalImage)
        isVerticalImage = [NSImage imageNamed:@"NSTruthHResizeCursor"];

    if (isVerticalImage)
    {
        kfIsVerticalResizeCursor = [[NSCursor alloc] initWithImage:isVerticalImage
                                                           hotSpot:NSMakePoint(8,8)];
    }

    if (!isNotVerticalImage)
        isNotVerticalImage = [NSImage imageNamed:@"NSTruthVResizeCursor"];

    if (isNotVerticalImage)
    {
        kfNotIsVerticalResizeCursor = [[NSCursor alloc] initWithImage:isNotVerticalImage
                                                           hotSpot:NSMakePoint(8,8)];
    }

    if (kfIsVerticalResizeCursor == nil)
    {
        kfIsVerticalResizeCursor = [[NSCursor arrowCursor] retain];
        NSLog(@"Warning - no horizontal resizing cursor located.  Please report this as a bug.");
    }
    if (kfNotIsVerticalResizeCursor == nil)
    {
        kfNotIsVerticalResizeCursor = [[NSCursor arrowCursor] retain];
        NSLog(@"Warning - no vertical resizing cursor located.  Please report this as a bug.");
    }
}

- (void)awakeFromNib
{
    [self kfRecalculateDividerRects];
    [self setPositionAutosaveName: [super autosaveName]];
}

- (void)dealloc
{
    [self setDelegate:nil];
    [self setPositionAutosaveName:@""];
    [kfCollapsedSubviews release];
    [kfDividerRects release];
    [kfIsVerticalResizeCursor release];
    [kfNotIsVerticalResizeCursor release];
    [super dealloc];
}

/******************
 * Main processing
 ******************/
#pragma mark Main processing:

- (void)mouseDown:(NSEvent *)theEvent
{
    // All coordinates are major axis coordinates unless otherwise specified.  See the top of the file
    // for an explanation of major and minor axes.
    float   minorDim;                                           // common dimension of all subviews
    NSInteger     divider;                                            // index of a divider being dragged
    float   mouseCoord, mouseToDividerOffset;                   // the mouse holds on to whatever part of the divider it grabs onto
    float   dividerThickness;                                   
    float   dividerCoord, prevDividerCoord;                     
    float   hardMinCoord, hardMaxCoord;                         // absolute boundaries for dividerCoord
    float   delMinCoord, delMaxCoord;                           // boundaries for dividerCoord according to the delegate (not absolute)
    NSView *firstSubview, *secondSubview;                       // subviews above and below the divider (if !isVertical)
    float   firstSubviewMinCoord, secondSubviewMaxCoord;        // top of the first, bottom of the second (if !isVertical)
    BOOL    firstSubviewCanCollapse, secondSubviewCanCollapse;  
    NSDate *distantFuture;
    float (*splitPosConstraintFunc)(id, SEL, ...);              // delegate supplied function to constrain dividerCoord

    // setup
    minorDim = KFMINORDIMOFSIZE([self frame].size);
    dividerThickness = [self dividerThickness];
    distantFuture = [NSDate distantFuture];

    // PRECOMPUTATION - we do as much as we can before starting the event loop.
    
    // figure out which divider is being dragged
    mouseCoord = KFMAJORCOORDOFPOINT([self convertPoint:[theEvent locationInWindow] fromView:nil]);
    divider = [self kfGetDividerAtMajCoord:mouseCoord];
    if (divider == NSNotFound)
    {
        return;
    }

    // if the event is a double click we let the delegate deal with it
    if ([theEvent clickCount] > 1)
    {
        if ([kfDelegate respondsToSelector:@selector(splitView:didDoubleClickInDivider:)])
        {
            [kfDelegate splitView:self didDoubleClickInDivider:divider];
            return;
        }
    }

    // firstSubview is the subview above (left) of the divider
    // secondSubview is the subview below (right) of the divider
    firstSubview = [[self subviews] objectAtIndex:divider];
    secondSubview = [[self subviews] objectAtIndex:divider+1];

    // set firstSubviewMinCoord and secondSubviewMaxCoord.  Here's a little diagram:
    //     ------------ <- firstSubviewMinCoord
    //
    //
    //
    //
    //     ------------ <- dividerCoord (not set yet)
    //     ------------
    //
    //
    //     ------------ <- secondSubviewMaxCoord
    if (![self isSubviewCollapsed:firstSubview])
    {
        firstSubviewMinCoord = KFMAJORCOORDOFPOINT([firstSubview frame].origin);
    }
    else
    {
        firstSubviewMinCoord = KFMAJORCOORDOFPOINT([[kfDividerRects objectAtIndex:divider] rectValue].origin);
    }
    if (![self isSubviewCollapsed:secondSubview])
    {
        secondSubviewMaxCoord = KFMAJORCOORDOFPOINT([secondSubview frame].origin) + KFMAJORDIMOFSIZE([secondSubview frame].size);
    }
    else
    {
        secondSubviewMaxCoord = KFMAJORCOORDOFPOINT([[kfDividerRects objectAtIndex:divider] rectValue].origin) + dividerThickness;
    }

    // hardMinCoord and hardMaxCoord are the absolute minimum and maximum values that may be
    // assigned to dividerCoord. delMinCoord and delMaxCoord are minimum and maximum values
    // for dividerCoord that are supplied by the delegate. These last are _not_ absolute: if the
    // delegate allows collapsing of subviews then dividerCoord can snap from delMinCoord to
    // hardMinCoord if the user drags the divider more than halfway across the region between them.
    // See Apple's NSSplitView documenation under - splitView:canCollapseSubview:.
    
    hardMinCoord = firstSubviewMinCoord;
    hardMaxCoord = secondSubviewMaxCoord - dividerThickness;

    delMinCoord = hardMinCoord;
    delMaxCoord = hardMaxCoord;
    
    if ([kfDelegate respondsToSelector:@selector(splitView:constrainMinCoordinate:ofSubviewAt:)])
    {
        delMinCoord = [kfDelegate splitView:self
                     constrainMinCoordinate:delMinCoord
                                ofSubviewAt:divider];
    }
    if ([kfDelegate respondsToSelector:@selector(splitView:constrainMaxCoordinate:ofSubviewAt:)])
    {
        delMaxCoord = [kfDelegate splitView:self
                     constrainMaxCoordinate:delMaxCoord
                                ofSubviewAt:divider];
    }

    delMinCoord = (delMinCoord < hardMinCoord) ? hardMinCoord : delMinCoord;
    delMaxCoord = (delMaxCoord > hardMaxCoord) ? hardMaxCoord : delMaxCoord;

    if (delMinCoord > delMaxCoord)
    {
        // this follows apple's implementation.  It says that if the delegate does
        // not supply any zone where the divider can sit without collapsing a subview then 
        // ignore the delegate.  The other option would be to always collapse to one subview
        // or the other, if one or both of the subviews are collasible.  That could be a bit of a UI
        // problem, because the user could try to drag a subview and have nothing happen.
        delMinCoord = hardMinCoord;
        delMaxCoord = hardMaxCoord;
    }

    firstSubviewCanCollapse = NO;
    secondSubviewCanCollapse = NO;
    if ([kfDelegate respondsToSelector:@selector(splitView:canCollapseSubview:)])
    {
        firstSubviewCanCollapse = [kfDelegate splitView:self canCollapseSubview:firstSubview];
        secondSubviewCanCollapse = [kfDelegate splitView:self canCollapseSubview:secondSubview];
    }

    // The delegate may constrain the possible values for dividerCoord.
    // Since this method will be called repeatedly we cache a pointer to it.
    splitPosConstraintFunc = NULL;
    if ([kfDelegate respondsToSelector:@selector(splitView:constrainSplitPosition:ofSubviewAt:)])
    {
        splitPosConstraintFunc = (float (*)(id, SEL, ...))[kfDelegate methodForSelector:@selector(splitView:constrainSplitPosition:ofSubviewAt:)];
    }

    // When the user grabs and drags the divider he holds onto that
    // particular spot while dragging.
    // mouseToDividerOffset is the difference between dividerCoord (the top of
    // the divider) and mouseCoord. 
    mouseToDividerOffset = KFMAJORCOORDOFPOINT([[kfDividerRects objectAtIndex:divider] rectValue].origin) - mouseCoord;

    // EVENT-LOOP
    prevDividerCoord = 1000000; // something non-sensical
    do
    {
        mouseCoord = KFMAJORCOORDOFPOINT([self convertPoint:[theEvent locationInWindow] fromView:nil]);
        dividerCoord = mouseCoord + mouseToDividerOffset;
        if (splitPosConstraintFunc != NULL)
        {
            dividerCoord = (*splitPosConstraintFunc)(kfDelegate,
                                                     @selector(splitView:constrainSplitPosition:ofSubviewAt:),
                                                     self,
                                                     dividerCoord,
                                                     divider);
        }

        // There are five regions where user may have dragged the divider:
        //     collapse first subview
        //     stick the divider to delMinCoord
        //     move freely
        //     stick the divider to delMaxCoord
        //     collapse the second subview
        if ( hardMinCoord == hardMaxCoord )
        {
            // special case: divider is pinned.  It is possible to collapse both subviews.
            [self setSubview:firstSubview isCollapsed:firstSubviewCanCollapse];
            [self setSubview:secondSubview isCollapsed:secondSubviewCanCollapse];
            dividerCoord = hardMinCoord;
        }
        else if ( firstSubviewCanCollapse &&
                  dividerCoord < hardMinCoord + (delMinCoord - hardMinCoord)/2)
        {
            // collapse first subview
            [self setSubview:secondSubview isCollapsed:NO];
            [self setSubview:firstSubview isCollapsed:YES];
            dividerCoord = hardMinCoord;
        }
        else if ( dividerCoord < delMinCoord )
        {
            // stick to delMinCoord
            [self setSubview:firstSubview isCollapsed:NO];
            [self setSubview:secondSubview isCollapsed:NO];
            dividerCoord = delMinCoord;
        }
        else if ( dividerCoord < delMaxCoord )
        {
            // move freely
            [self setSubview:firstSubview isCollapsed:NO];
            [self setSubview:secondSubview isCollapsed:NO];
        }
        else if ( !secondSubviewCanCollapse ||
                  dividerCoord < hardMaxCoord - (hardMaxCoord - delMaxCoord)/2 )
        {
            // stick to delMaxCoord
            [self setSubview:firstSubview isCollapsed:NO];
            [self setSubview:secondSubview isCollapsed:NO];
            dividerCoord = delMaxCoord;
        }
        else
        {
            // collapse second subview
            [self setSubview:firstSubview isCollapsed:NO];
            [self setSubview:secondSubview isCollapsed:YES];
            dividerCoord = hardMaxCoord;
        }

        if (prevDividerCoord != dividerCoord)
        {
            // Position and resize elements.  A collapsing subview's frame size doesn't change,
            // the subview just gets moved way offscreen (as in NSSplitView).
            // The diagram may help:
            //
            //     ------------ <- firstSubviewMinCoord
            //
            //
            //
            //     ------------ <- dividerCoord
            //     ------------ <- dividerCoord + dividerThickness
            //
            //
            //     ------------ <- secondSubviewMaxCoord
            
            [kfNotificationCenter postNotificationName:NSSplitViewWillResizeSubviewsNotification object:self];

            // divider
            [self kfPutDivider:divider atMajCoord:dividerCoord];

            // firstSubview
            if (![self isSubviewCollapsed:firstSubview])
            {
                NSRect newFrame;
                
                newFrame.origin = KFPOINTWITHMAJMIN(firstSubviewMinCoord,0);
                newFrame.size = KFSIZEWITHMAJMIN(dividerCoord - firstSubviewMinCoord, minorDim);
                
                if (!NSEqualRects([firstSubview frame],newFrame))
                {
                    [firstSubview setFrame:newFrame];
                    [firstSubview setNeedsDisplay:YES];
                }
            }
            else
            {
                [firstSubview setFrameOrigin:KFOffScreenPoint];
            }

            // secondSubview
            if (![self isSubviewCollapsed:secondSubview])
            {
                NSRect newFrame;
                
                newFrame.origin = KFPOINTWITHMAJMIN(dividerCoord + dividerThickness, 0);
                newFrame.size = KFSIZEWITHMAJMIN(secondSubviewMaxCoord - (dividerCoord + dividerThickness), minorDim);
                
                if (!NSEqualRects([secondSubview frame],newFrame))
                {
                    [secondSubview setFrame:newFrame];
                    [secondSubview setNeedsDisplay:YES];
                }                
            }
            else
            {
                [secondSubview setFrameOrigin:KFOffScreenPoint];
            }
            
            [kfNotificationCenter postNotificationName:NSSplitViewDidResizeSubviewsNotification object:self];

            prevDividerCoord = dividerCoord;
        }

        // get the next relevant event
        theEvent = [NSApp nextEventMatchingMask:NSLeftMouseDraggedMask|NSLeftMouseUpMask
                                      untilDate:distantFuture
                                         inMode:NSEventTrackingRunLoopMode
                                        dequeue:YES];
        
    } while ([theEvent type] == NSLeftMouseDragged);
    
    // inform delegate that user has finished dragging divider
    if ([kfDelegate respondsToSelector:@selector(splitView:didFinishDragInDivider:)])
    {
        [kfDelegate splitView:self didFinishDragInDivider:divider];
    }    
}

// Call this method to retile the subviews, not adjustSubviews.
// It 1) dispatches will and did resize subviews notifications
//    2) calls the appropriate method to do the retiling.  That's a method of
//       the delegate if it has one and the default adjustSubviews otherwise.
//    3) cleans up some other layout, like divider positions
- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize
{    
    [kfNotificationCenter postNotificationName:NSSplitViewWillResizeSubviewsNotification object:self];
    
    if ([kfDelegate respondsToSelector:@selector(splitView:resizeSubviewsWithOldSize:)])
    {
        [kfDelegate splitView:self resizeSubviewsWithOldSize:oldBoundsSize];
    }
    else
    {
        [self adjustSubviews];
    }
    
    [self kfRecalculateDividerRects];
    [self kfMoveCollapsedSubviewsOffScreen];
    
    [kfNotificationCenter postNotificationName:NSSplitViewDidResizeSubviewsNotification object:self];
}


// See Apple's NSSplitView docs.  However, note that in general you want to call
// resizeSubviewsWithOldSize:, not this method.  The exception is that you might
// want to call adjustSubviews from splitView:resizeSubviewsWithOldSize: in the
// the delegate
- (void)adjustSubviews
{
    int i, numSubviews;
    NSArray *subviews;
    
    // The 'thickness' of a subview will mean the amount of space along
    // the major axis that the subview occupies in the splitview. 
    // We work in integral values, though actual thicknesses are floats.
    // In the current OS, the floats actually have integral values.
    //
    // Ex 1: The thickness of a collapsed subview is 0.
    // Ex 2: For an uncollapsed subview in a horizontal (standard direction) splitview,
    //       thickness means height.
    unsigned *subviewThicknesses;
    
    // setup 
    subviews = [self subviews];
    numSubviews = [subviews count];
    if (numSubviews == 0)
    {
        return;
    }
    
    subviewThicknesses = malloc(sizeof(unsigned)*numSubviews);
    
    // Fill out subviewThicknesses array.
    // Also keep track of the total thickness of all subviews, and 
    // of the first expanded subview
    unsigned totalSubviewThicknesses = 0;
    NSInteger firstExpandedSubviewIndex = NSNotFound;
    for (i = 0; i < numSubviews; i++)
    {
        NSView *subview = [subviews objectAtIndex:i];
        if (![self isSubviewCollapsed:subview])
        {
            subviewThicknesses[i] = floor(KFMAJORDIMOFSIZE([subview frame].size));
            totalSubviewThicknesses += subviewThicknesses[i];
            if (firstExpandedSubviewIndex == NSNotFound) { firstExpandedSubviewIndex = i; }
        }
        else
        {
            subviewThicknesses[i] = 0;
        }
    }
    
    // Compute new thicknesses for subviews.
    
    // In the end, the subview thicknesses should sum to the thickness of the splitview minus the space occupied by dividers.
    unsigned targetTotalSubviewsThickness = KFMAX(floor(KFMAJORDIMOFSIZE([self frame].size) - [self dividerThickness]*(numSubviews - 1)), 0);
    
    // If at least one of the subviews has positive thickness
    if (totalSubviewThicknesses != 0)
    {
        // then we can scale all the thicknesses 
        kfScaleUInts(subviewThicknesses, numSubviews, targetTotalSubviewsThickness);        
    }
    else // otherwise we'll have to expand one of the subviews to fill the entire space
    {
        if (firstExpandedSubviewIndex != NSNotFound)
        {
            subviewThicknesses[firstExpandedSubviewIndex] = targetTotalSubviewsThickness;
        }
        else
        {
            subviewThicknesses[0] = targetTotalSubviewsThickness;
        }
    }
    
    
    // layout subviews
    [self kfLayoutSubviewsUsingThicknesses:subviewThicknesses];
    
    // cleanup 
    free(subviewThicknesses);
}

// Required: Sum of all subviewThicknesses <= splitViewThickness - dividersThickness.   
// If the splitview has positive available space for subviews, then one of the supplied subview thicknesses
// must also be positive.  Extra space will be dumped into the last subview with positive thickness.  
// See adjustSubviews for the definition of 'thickness'.
// 
// Does not currently put collapsed subviews off screen or do divider placement.
// Could be done efficiently here, but would duplicate functionality of other methods.
- (void)kfLayoutSubviewsUsingThicknesses:(unsigned *)subviewThicknesses
{
    NSInteger i, lastPositiveThicknessSubviewIndex, numSubviews;
    float minorDimOfSplitViewSize;
    float curMajAxisPos, dividerThickness;
    NSArray *subviews;
    
    // setup
    subviews = [self subviews];
    numSubviews = [subviews count];
    minorDimOfSplitViewSize = KFMINORDIMOFSIZE([self frame].size);
    dividerThickness = [self dividerThickness];
    
    // Compute lastPositiveThicknessSubviewIndex.
    lastPositiveThicknessSubviewIndex = NSNotFound;
    for (i = numSubviews - 1; i >= 0; i--)
    {
        if (subviewThicknesses[i] != 0)
        {
            lastPositiveThicknessSubviewIndex = i;
            break;
        }
    }
    
    // We walk down the major axis, setting subview frames as we go.
    curMajAxisPos = 0;
    for (i = 0; i < numSubviews; i++)
    {
        NSView *subview = [subviews objectAtIndex:i];
        
        float newSubviewThickness = -1; // sentinel value, meaning "do not change"

        if (subviewThicknesses[i] == 0) // If subview should have no thickness
        {
            // then shrink its frame if it is uncollapsed.
            if (![self isSubviewCollapsed:subview]) 
            {
                newSubviewThickness = 0;
            }
        }
        else // If supplied thickness is positive
        {            
            // make sure the subview isn't collapsed.
            if ([self isSubviewCollapsed:subview])
            {
                [self setSubview:subview isCollapsed:NO];
            }
            
            
            // If this is the last subview that we're going to give a positive thickness
            if (i == lastPositiveThicknessSubviewIndex)
            {
                // we overrule the given the given value and just fill all available area.
                float remainingDividersThickness = (numSubviews - 1 - i)*dividerThickness;
                float splitViewThickness = KFMAJORDIMOFSIZE([self frame].size);
                
                newSubviewThickness = KFMAX(splitViewThickness - curMajAxisPos - remainingDividersThickness, 0); 
            }
            else // If this isn't the last subview that we're going to set to a positive thickness
            {
                // use the supplied thickness.
                newSubviewThickness = subviewThicknesses[i];
            }
        }
        
        // If we found a new subview thickness
        if (newSubviewThickness != -1)
        {
            // set the subview's frame accordingly
            NSPoint newSubviewOrigin = KFPOINTWITHMAJMIN(curMajAxisPos, 0);
            NSSize  newSubviewSize   = KFSIZEWITHMAJMIN(newSubviewThickness, minorDimOfSplitViewSize);
            NSRect  newFrame         = NSMakeRect(newSubviewOrigin.x, newSubviewOrigin.y,
                                                  newSubviewSize.width, newSubviewSize.height);
            
            if (!NSEqualRects([subview frame],newFrame))
            {
                [subview setFrame:newFrame];
                [subview setNeedsDisplay:YES];
            }
            
            // and advance down the major axis.
            curMajAxisPos += newSubviewThickness;
        }

        // Account for divider thickness.
        if (i < numSubviews - 1)
        {
            curMajAxisPos += dividerThickness;
        }
    }    
}

- (void)kfMoveCollapsedSubviewsOffScreen
{
    NSEnumerator *collapsedSubviewEnumerator = [kfCollapsedSubviews objectEnumerator];
    NSView *subview;
    while ((subview = [collapsedSubviewEnumerator nextObject]))
    {
        [subview setFrameOrigin:KFOffScreenPoint];
    }
}


- (void)drawRect:(NSRect)rect
{
    int i, numDividers;

    numDividers = [kfDividerRects count];
    for (i = 0; i < numDividers; i++)
    {
        [self drawDividerInRect:[[kfDividerRects objectAtIndex:i] rectValue]];
    }
}

// returns the index ('offset' in Apple's docs) of the divider under the
// given coordinate, or NSNotFound if there isn't a divider there.
- (int)kfGetDividerAtMajCoord:(float)coord
{
    NSInteger i, numDividers, result;
    float curDividerMinimumMajorCoord, dividerThickness;
        
    numDividers = [kfDividerRects count];
    result = NSNotFound;
    dividerThickness = [self dividerThickness];
    
    for (i = 0; i < numDividers; i++)
    {
        curDividerMinimumMajorCoord = KFMAJORCOORDOFPOINT([[kfDividerRects objectAtIndex:i] rectValue].origin);
        if (curDividerMinimumMajorCoord <= coord && coord < curDividerMinimumMajorCoord + dividerThickness)
        {
            result = i;
            break;
        }
    }

    return result;
}

- (void)kfPutDivider:(NSUInteger)offset atMajCoord:(float)coord
{
    NSPoint newOrigin;
    NSSize  newSize;
    NSRect  newFrame;

    while ([kfDividerRects count] <= offset)
    {
        [kfDividerRects addObject:[NSValue valueWithRect:NSZeroRect]];
    }
    
    newOrigin = KFPOINTWITHMAJMIN(coord,0);
    newSize = KFSIZEWITHMAJMIN([self dividerThickness], KFMINORDIMOFSIZE([self frame].size));
    newFrame = NSMakeRect(newOrigin.x, newOrigin.y, newSize.width, newSize.height);
    
    if (!NSEqualRects([[kfDividerRects objectAtIndex:offset] rectValue], newFrame))
    {
        [kfDividerRects replaceObjectAtIndex:offset withObject:[NSValue valueWithRect:newFrame]];
        [self setNeedsDisplayInRect:newFrame];
        [[[self subviews] objectAtIndex:offset]   setNeedsDisplay:YES];
        [[[self subviews] objectAtIndex:offset+1] setNeedsDisplay:YES];

    }
}

// positions all dividers based on the current location of the subviews
- (void)kfRecalculateDividerRects
{
    float curMajAxisPos, dividerThickness;
    NSView* subview;
    NSArray* subviews;
    NSUInteger numSubviews, i;
    
    dividerThickness = [self dividerThickness];
    subviews = [self subviews];
    numSubviews = [subviews count];
    
    curMajAxisPos = 0;
    for (i = 0; (i + 1) < numSubviews; i++)
    {
        subview = [subviews objectAtIndex:i];
        if (![self isSubviewCollapsed:subview])
        {
            curMajAxisPos += KFMAJORDIMOFSIZE([subview frame].size);
        }

        [self kfPutDivider:i atMajCoord:curMajAxisPos];
        curMajAxisPos += dividerThickness;
    }
    
    NSUInteger numDividerRects = [kfDividerRects count];
    if ((numDividerRects + 1) > numSubviews)
    {
        [kfDividerRects removeObjectsInRange:NSMakeRange(numSubviews-1,numDividerRects - numSubviews + 1)];
    }
    
    [[self window] invalidateCursorRectsForView:self];
}

- (void)resetCursorRects
{
    int i, numDividers;

    numDividers = [kfDividerRects count];
    for (i = 0; i < numDividers; i++)
    {
        [self addCursorRect:[[kfDividerRects objectAtIndex:i] rectValue]
                     cursor:kfCurrentResizeCursor];
    }
}

/******************
 * Accessors
 ******************/

- (void)setVertical:(BOOL)flag
{
    [super setVertical:flag]; 
    kfIsVertical = flag;
    if (kfIsVertical)
    {
        kfCurrentResizeCursor = kfIsVerticalResizeCursor;
    }
    else
    {
        kfCurrentResizeCursor = kfNotIsVerticalResizeCursor;
    }
}

- (id)delegate
{
    return kfDelegate;
}

// automatically registers the delegate for relevant notifications, and unregisters
// the old delegate for those same notifications.
- (void)setDelegate:(id)delegate
{
    NSArray* delegateAutoRegNotifications;
    NSArray* delegateMethodNames;
    int i, numAutoRegNotifications;
    SEL methodSelector;

    delegateAutoRegNotifications = [NSArray arrayWithObjects:
        NSSplitViewWillResizeSubviewsNotification,
        NSSplitViewDidResizeSubviewsNotification,
        KFSplitViewDidCollapseSubviewNotification,
        KFSplitViewDidExpandSubviewNotification, nil];
    delegateMethodNames = [NSArray arrayWithObjects:
                           @"splitViewWillResizeSubviews:",
                           @"splitViewDidResizeSubviews:",
                           @"splitViewDidCollapseSubview:",
                           @"splitViewDidExpandSubview:", nil];
    numAutoRegNotifications = [delegateAutoRegNotifications count];

    if (kfDelegate)
    {
        for (i = 0; i < numAutoRegNotifications; i++)
        {
            [kfNotificationCenter removeObserver:kfDelegate
                                            name:[delegateAutoRegNotifications objectAtIndex:i]
                                          object:self];
        }
    }

    kfDelegate = delegate;

    if (kfDelegate)
    {
        for (i = 0; i < numAutoRegNotifications; i++)
        {
            methodSelector = sel_registerName([[delegateMethodNames objectAtIndex:i] cString]);
            if ([kfDelegate respondsToSelector:methodSelector])
            {
                [kfNotificationCenter addObserver:kfDelegate
                                         selector:methodSelector
                                             name:[delegateAutoRegNotifications objectAtIndex:i]
                                           object:self];
            }
        }
    }
}

- (BOOL)isSubviewCollapsed:(NSView *)subview
{
    return [kfCollapsedSubviews containsObject:subview];
}

- (void)setSubview:(NSView *)subview isCollapsed:(BOOL)flag
{
    NSDictionary *subviewDictionary;
    
    if (flag != [self isSubviewCollapsed:subview])
    {
        subviewDictionary = [NSDictionary dictionaryWithObject:subview forKey:@"subview"];
        if (flag)
        {
            [kfCollapsedSubviews addObject:subview];
            [kfNotificationCenter postNotificationName:KFSplitViewDidCollapseSubviewNotification
                                                object:self
                                              userInfo:subviewDictionary];
        }
        else
        {
            [kfCollapsedSubviews removeObject:subview];
            [kfNotificationCenter postNotificationName:KFSplitViewDidExpandSubviewNotification
                                                object:self
                                              userInfo:subviewDictionary];
        }
    }
}


/**********************
 * Position saving
 **********************/
#pragma mark Position saving:

// FOR DOCUMENTATION OF POSITION SAVING METHODS SEE APPLE'S NSWINDOW DOCS

+ (void)removePositionUsingName:(NSString *)name
{
    [[NSUserDefaults standardUserDefaults] removeObjectForKey:[[self class] kfDefaultsKeyForName:name]];
}

- (void)savePositionUsingName:(NSString *)name
{
    NSString *key = [[self class] kfDefaultsKeyForName:name];
    NSString *prop = [self plistObjectWithSavedPosition];
    [kfDefaults setObject:prop forKey:key];
}

- (BOOL)setPositionUsingName:(NSString *)name
{
    BOOL result;
    id object;

    object = [kfDefaults objectForKey:[[self class] kfDefaultsKeyForName:name]];
    if (object)
    {
        [self setPositionFromPlistObject:object];
        result = YES;
    }
    else
    {
        result = NO;
    }

    return result;
}

- (BOOL)setPositionAutosaveName:(NSString *)name
{
    if ([name isEqualToString:@""])
    {
        name = nil;
    }

    if ([kfInUsePositionNames containsObject:name])
    {
        return NO;
    }

    if (kfPositionAutosaveName)
    {
        [kfInUsePositionNames removeObject:kfPositionAutosaveName];
        [kfPositionAutosaveName autorelease];
    }

    kfPositionAutosaveName = [name copy];
    if (kfPositionAutosaveName)
    {
        [self setPositionUsingName:kfPositionAutosaveName];
        [kfInUsePositionNames addObject:kfPositionAutosaveName];
        [kfNotificationCenter addObserver:self
                                 selector:@selector(kfSavePositionUsingAutosaveName:)
                                     name:NSSplitViewDidResizeSubviewsNotification
                                   object:self];
    }
    else
    {
        [kfNotificationCenter removeObserver:self
                                        name:NSSplitViewDidResizeSubviewsNotification
                                      object:self];
    }

    return YES;
}

- (NSString *)positionAutosaveName
{
    return kfPositionAutosaveName;
}


static NSString *savedPositionVersionKey 		= @"version";
static NSString *savedPositionSubviewsKey 		= @"subviews";
static NSString *savedPositionSubviewFrameKey		= @"frame";
static NSString *savedPositionSubviewIsCollapsedKey	= @"collapsed";
static NSString *savedPositionIsVerticalKey		= @"isVertical";

- (void)setPositionFromPlistObject:(id)plistObject
{
    if ([plistObject isKindOfClass:[NSDictionary class]])
    {
        NSDictionary *positionDict = (NSDictionary *)plistObject;
        
        // check position data format version
        if ([[positionDict objectForKey:savedPositionVersionKey] intValue] == 2)
        {
            NSArray *subviews, *subviewPositionsArray;
            int numSubviews, numSavedSubviews, numSettableSubviews, i;
            
            // set subview positions
            subviews = [self subviews];
            numSubviews = [subviews count];
            subviewPositionsArray = [positionDict objectForKey:savedPositionSubviewsKey];
            
            // what if the number of saved subview records and the actual number of subviews don't match?
            // we'll set positions until we run out of either subviews or data records
            numSavedSubviews = [subviewPositionsArray count];
            numSettableSubviews = (numSubviews < numSavedSubviews) ? numSubviews : numSavedSubviews;
            
            for (i = 0; i < numSettableSubviews; i++)
            {
                NSView *subview;
                NSDictionary *subviewPositionData;
            
                subview = [subviews objectAtIndex:i];
                subviewPositionData = [subviewPositionsArray objectAtIndex:i];
                
                // subview data consists of frame and collapse state
                [subview setFrame:NSRectFromString([subviewPositionData objectForKey:savedPositionSubviewFrameKey])];
                [self setSubview:subview isCollapsed:[[subviewPositionData objectForKey:savedPositionSubviewIsCollapsedKey] boolValue]];
            }
                        
            // set isVertical
            [self setVertical:[[positionDict objectForKey:savedPositionIsVerticalKey] boolValue]];            
        }
    }
    
    [self resizeSubviewsWithOldSize:[self bounds].size];    
}

- (id)plistObjectWithSavedPosition
{ 
    NSMutableDictionary *positionDict = [NSMutableDictionary dictionary];
    
    // save position data format version
    [positionDict setObject:[NSNumber numberWithInt:2] forKey:savedPositionVersionKey];
    
    // save subview positions
    NSArray *subviews;
    NSMutableArray *subviewPositionsArray;
    int numSubviews, i;
    
    subviews = [self subviews];
    numSubviews = [subviews count];
    subviewPositionsArray = [NSMutableArray array];
    
    for (i = 0; i < numSubviews; i++)
    {
        NSView *subview;
        NSDictionary *subviewPositionData;
        
        subview = [subviews objectAtIndex:i];
        
        // subview data consists of frame and collapse state
        subviewPositionData = [NSDictionary dictionaryWithObjectsAndKeys:
            NSStringFromRect([subview frame]), 					savedPositionSubviewFrameKey,
            [NSNumber numberWithBool:[self isSubviewCollapsed:subview]], 	savedPositionSubviewIsCollapsedKey,
            nil];
        [subviewPositionsArray addObject:subviewPositionData];
    }
        
    [positionDict setObject:subviewPositionsArray forKey:savedPositionSubviewsKey];
    
    // save isVertical
    BOOL isVertical = [self isVertical];
    [positionDict setObject:[NSNumber numberWithBool:isVertical]
                     forKey:savedPositionIsVerticalKey];
    
    return positionDict;
}


+ (NSString *)kfDefaultsKeyForName:(NSString *)name
{
    return [@"KFSplitView Position " stringByAppendingString:name];
}

- (void)kfSavePositionUsingAutosaveName:(id)sender
{
    [self savePositionUsingName:kfPositionAutosaveName];
}
@end
#undef KFMAJORCOORDOFPOINT
#undef KFMINORCOORDOFPOINT
#undef KFMAJORDIMOFSIZE
#undef KFMINORDIMOFSIZE
#undef KFPOINTWITHMAJORMINOR
#undef KFSIZEWITHMAJORMINOR

#undef KFMAX

